package socket;

import com.sun.corba.se.impl.orbutil.ObjectStreamClassUtil_1_3;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

/**
 * 聊天室服务器
 */
public class Server {
    /*
    java.ner.ServerSocket运行服务端
    主要有两个作用:
    1:向系统申请服务端口(客户端就是通过这个端口与当前服务器进行连接的)
    2:监听服务器端口,一旦一个客户端申请建立了连接则接受该链接并返回一个Socket实例.
    通过这个Socket与客户端对等连接进行双向交互
    我们可以把Socket想象为"电话",那么ServerSocket则相当于某个客服中心的"总机"
    总机下面连接若干部电话,每个客户端打电话来总机都会分配一个电话响并将其连接
     */
    private ServerSocket serverSocket;
    /*
    存放所有客户端的输出流,用于广播消息
    由于所有ClientHandler都是Server的内部类,而内部类可以访问外部类成员,因此
    在外部类上定义一个属性可以作为内部类实例之间共享数据使用
     */
    //private PrintWriter[] allOut = {}
    private Collection<PrintWriter> allOut = new ArrayList<>();

    public Server() {

        try {
            /**
             * 实例化ServerSocket时需要指定服务器端口,客户端通过这个端口与服务器建立连接
             * 该端口不能和当前系统其他应用程序的端口重复,否则会抛出异常:
             * java.net.BindException:address already in use
             */
            System.out.println("正在启动服务端....");
            serverSocket = new ServerSocket(8088);//0-65535之间
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start() {
        try {
            while (true) {
                System.out.println("等待客户端连接....");
                Socket socket = serverSocket.accept();//阻塞
                System.out.println("一个客户端连接了!");
                //启动一个线程与该客户端交互
                ClientHandler handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();

    }

    /**
     * 该线程任务用于与指定的客户端进行交互
     */
    private class ClientHandler implements Runnable {
        private Socket socket;
        private String host;//记录客户端地址信息

        public ClientHandler(Socket socket) {
            this.socket = socket;
            //通过socket获取远端计算机地址信息的字符串格式
            host = socket.getInetAddress().getCanonicalHostName();
        }

        public void run() {
            PrintWriter pw = null;
            try {
       /*
            Socket提供的方法:
            InputStream getInputStream()
            获取一个字节输入流,用于读取远端计算机发送过来的数据
             */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);

                //通过socket获取输出流,用于将消息发送给该客户端
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
                BufferedWriter bw = new BufferedWriter(osw);
                pw = new PrintWriter(bw, true);
                //将该客户端的输出流存入共享数组allOut中,供其他ClientHandler转发消息
                //synchronized (this){不行,不同的线程看到的ClientHandler不同
                //synchronized (allOut){不行,因为会对数组扩容(相当于创建了新的数组对象)
                synchronized (Server.this) {//可以执行外部类Server对象
//                    //1:将allOut扩容
//                    allOut = Arrays.copyOf(allOut, allOut.length + 1);
//                    //2:将当前客户端的输出流存入allOut数组最后一个位置上(刚扩容出来的格子里)
//                    allOut[allOut.length - 1] = pw;
                    allOut.add(pw);
                }

                sendMessage(host + "上线了,当前在线人数:" + allOut.size());

                String message;
            /*
            这里使用缓冲字符输入流的readLine方法读取一行来自客户端发送过来的字符串
            该方法调用后出现"阻塞",直到客户端确实发送了一行字符串适该方法猜会将这行字符串返回.
            如果返回值为null,则说明客户端断开了连接了(客户端调用socket.close()正常断开连接)
            如果客户端强行中断程序(没有调用socket.close())则这里会抛出异常:
            java.net.SocketException: Connection reset
             */
                while ((message = br.readLine()) != null) {
                    System.out.println(host + "说:" + message);

                    sendMessage(host + "说:" + message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //这里处理客户端断开连接后的操作
                //  synchronized (Server.this) {
                synchronized (allOut) {//对于集合而言,直接锁集合对象即可
                    //将当前客户端的输出流从共享数组allOut中删除
//                    for (int i = 0; i < allOut.length; i++) {
//                        if (allOut[i] == pw) {
//                            allOut[i] = allOut[allOut.length - 1];
//                            allOut = Arrays.copyOf(allOut, allOut.length - 1);
//                            break;
//                        }
//                    }

                    allOut.remove(pw);
                }
            }
            sendMessage(host + "下线了,当前在线人数:" + allOut.size());

            try {
                socket.close();
            } catch (IOException e) {
            }
        }
    }

    public void sendMessage(String message) {
        synchronized (allOut) {

            // synchronized (Server.this) {
//                //将消息发送给所有客户端(遍历allOut数组)
//                for (int i = 0; i < allOut.length; i++) {
//                    allOut[i].println(message);
//                }
            for (PrintWriter str : allOut) {
                str.println(message);
            }
        }
    }
}

